package com.sen.paydemo.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.sen.paydemo.config.WxPayConfig;
import com.sen.paydemo.entity.OrderInfo;
import com.sen.paydemo.entity.RefundInfo;
import com.sen.paydemo.enums.OrderStatus;
import com.sen.paydemo.enums.wxpay.WxApiTpye;
import com.sen.paydemo.enums.wxpay.WxNotifyType;
import com.sen.paydemo.enums.wxpay.WxTradeState;
import com.sen.paydemo.service.OrderInfoService;
import com.sen.paydemo.service.PaymentInfoService;
import com.sen.paydemo.service.RefundInfoService;
import com.sen.paydemo.service.WxPayService;
import com.sen.paydemo.util.OrderNoUtils;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.service.payments.nativepay.NativePayService;
import com.wechat.pay.java.service.payments.nativepay.model.Amount;
import com.wechat.pay.java.service.payments.nativepay.model.PrepayRequest;
import com.wechat.pay.java.service.payments.nativepay.model.PrepayResponse;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 微信支付
 *
 * @作者: 韩宗森
 * @创建时间: 2024/1/9 19:10
 */
@Slf4j
@Service
public class WxPayServiceImpl implements WxPayService {

    @Resource
    private OrderInfoService orderInfoService;

    @Resource
    private WxPayConfig wxPayConfig;

    @Resource
    private PaymentInfoService paymentInfoService;

    @Resource
    private RefundInfoService refundInfoService;

    // 锁
    private final ReentrantLock lock = new ReentrantLock();


    /**
     * 创建订单, 调用Native支付接口
     *
     * @param productId
     * @return 二维码地址 和 订单号
     */
    @Override
    public Map<String, Object> nativePay(Integer productId) throws Exception {
        log.info("生成订单... ");
        // 生成订单
        OrderInfo orderInfo = orderInfoService.createOrderByProductId(productId);

        // 发起支付
        Map<String, Object> urlOrderMap = requestNativePay(orderInfo);
        return urlOrderMap;
//        return null;

    }

    // 微信h5支付
    @Override
    public Map<String, Object> h5Pay(Integer productId) {
        return null;
    }

    // 文档地址  https://pay.weixin.qq.com/docs/merchant/apis/native-payment/payment-notice.html
    @Override
    public void processOrder(Map<String, Object> bodyMap) throws GeneralSecurityException {
        log.info("处理订单");
        // 解密报文
        String plainText = decryptFromResource(bodyMap);
        // 将明文转map
        // 使用Fastjson 将 字符串转换为Map
        Map<String, Object> plainTextMap = JSON.parseObject(plainText, HashMap.class);

        String orderNo = (String) plainTextMap.get("out_trade_no");//商户系统内部订单号

        /*在对业务数据进行状态检查和处理之前，要采用数据锁进行并发控制，以避免函数重入造成的数据混乱*/
        // 尝试获取锁: 成功获取则立即返回true，获取失败则立即返回false, 不必一直等待锁的释放
        if (lock.tryLock()) {// 处理通知并发问题

            try {
                // 处理重复的通知
                // 接口调用的幂等性: 无论接口被调用多少次，产生的结果是一致的.
                String orderStatus = orderInfoService.getOrderStatus(orderNo); // 获取订单状态
                // 判断订单状态
                if (!OrderStatus.NOTPAY.getType().equals(orderStatus)) {
                    return;// 不是未支付的订单 ,直接返回,不处理
                }

                // 更新订单状态
                orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.SUCCESS);

                // 记录支付日志
                paymentInfoService.createPaymentInfo(plainText);
            } finally {
                // 主动释放锁
                lock.unlock();
            }
        }
    }

    // 取消订单
    @Override
    public void cancelOrder(String orderNo) throws Exception {
        // 调用微信支付的关单接口
        this.closeOrder(orderNo);
        // 更新商户端的订单状态
        orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.CANCEL);
    }

    // 查询订单
    @Override
    public String queryOrder(String orderNo) throws Exception {
        // 文档地址 https://pay.weixin.qq.com/docs/merchant/apis/native-payment/query-by-out-trade-no.html
        log.info("查单接口调用,订单号==> {}", orderNo);
        String url = String.format(WxApiTpye.QUERY_ORDER_BY_NO.getType(), orderNo);
        url = wxPayConfig.getDomain().concat(url).concat("?mchid=").concat(wxPayConfig.getMchId());
        // 创建get请求
        HttpGet httpGet = new HttpGet(url);
        httpGet.setHeader("Accept", "application/json");

        // 完成签名并执行请求
        CloseableHttpClient httpClient = wxPayConfig.getHttpClient();
        //完成签名并执行请求
        CloseableHttpResponse response = httpClient.execute(httpGet);
        try {
            String bodyAsString = EntityUtils.toString(response.getEntity());
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) {
                log.info("查单接口调用,返回结果 = " + bodyAsString);
            } else if (statusCode == 204) {
                log.info("查单接口调用...没返回");
            } else {
                log.info("查单接口调用,响应码 = " + statusCode + ", 返回结果 = " + bodyAsString);
                throw new IOException("request failed");
            }
            return bodyAsString;
        } finally {
            response.close();
            httpClient.close();
        }
    }

    /**
     * 根据订单号查询微信支付查单接口，核实订单状态
     * 如果订单已支付，则更新商户端订单状态
     * 如果订单未支付，则调用关单接口关闭订单，并更新商户端订单状态
     *
     * @param orderNo
     */
    @Override
    public void checkOrderStatus(String orderNo) throws Exception {
        log.warn("根据订单号核实订单状态==> {}", orderNo);
        //调用微信支付查单接口
        String result = this.queryOrder(orderNo);
        // 使用Fastjson 将 字符串转换为Map
        Map<String, Object> resultMap = JSON.parseObject(result, HashMap.class);
        //获取微信支付端的订单状态
        Object tradeState = resultMap.get("trade_state");
        // 判断订单状态
        if (WxTradeState.SUCCESS.getType().equals(tradeState)) {
            log.warn("核实订单已支付 ==> {}", orderNo);
            //如果订单已支付，则更新本地商户端订单状态
            orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.SUCCESS);
            // 记录支付日志
            paymentInfoService.createPaymentInfo(result);
        } else if (WxTradeState.NOTPAY.getType().equals(tradeState)) {
            log.warn("核实订单未支付 ==> {}", orderNo);
            // 如果订单未支付,则调用关单接口
            this.closeOrder(orderNo);
            //更新本地订单状态
            orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.CLOSED);

        }

    }

    @Override
    public void refund(String orderNo, String reason) throws Exception {
        // 文档地址 https://pay.weixin.qq.com/docs/merchant/apis/in-app-payment/create.html
        log.info("创建退款单记录==> {}", orderNo);
        // 根据订单的编号创建退款单
        RefundInfo refundInfo = refundInfoService.createRefundByOrderNo(orderNo, reason);

        log.info("调用微信退款API");

        // 调用统一下单API
        String url = wxPayConfig.getDomain().concat(WxApiTpye.DOMESTIC_REFUNDS.getType());
        // 创建远程请求对象
        HttpPost httpPost = new HttpPost(url);
        // 组装body参数
        Map<String, Object> paramsMap = new HashMap<>();
        paramsMap.put("out_trade_no", orderNo); // 商户订单号
        paramsMap.put("out_refund_no", refundInfo.getRefundNo()); // 商户退款单号
        paramsMap.put("reason", reason); // 退款原因
        paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxNotifyType.REFUND_NOTIFY.getType())); // 退款结果回调url
        paramsMap.put("out_trade_no", orderNo); // out_trade_no

        // 退款金额
        Map<String, Object> amountMap = new HashMap<>();
        amountMap.put("refund", refundInfo.getRefund()); //退款金额
        amountMap.put("total", refundInfo.getTotalFee()); //原订单金额
        amountMap.put("currency", "CNY"); //退款币种

        paramsMap.put("amount", amountMap); //退款金额

        // 使用Fastjson将 Map转换为JSON 字符串
        String jsonParams = JSON.toJSONString(paramsMap);
        log.info("退款请求体参数==> {}", jsonParams);

        StringEntity entity = new StringEntity(jsonParams, "utf-8");
        entity.setContentType("application/json");
        // 设置请求头 HTTP头参数
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");

        // 完成签名并执行请求
        CloseableHttpClient httpClient = wxPayConfig.getHttpClient();
        //完成签名并执行请求
        CloseableHttpResponse response = httpClient.execute(httpPost);
        try {
            String bodyAsString = EntityUtils.toString(response.getEntity());
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) {
                log.info("成功200,退款返回结果 = ", bodyAsString);
            } else if (statusCode == 204) {
                log.info("成功204,退款...没返回");
            } else {
                log.info("退款异常 ,响应码 = " + statusCode + ", 退款返回结果 = " + bodyAsString);
                throw new IOException("request failed");
            }
            // 更新订单状态
            orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.REFUND_PROCESSING);

            //更新退款单
            refundInfoService.updateRefund(bodyAsString);

        } finally {
            response.close();
            httpClient.close();
        }
    }

    // 查询退款单
    @Override
    public String queryRefund(String refundNo) throws Exception {
        // 文档地址 https://pay.weixin.qq.com/docs/merchant/apis/native-payment/query-by-out-trade-no.html
        log.info("查询退款单接口调用,订单号==> {}", refundNo);
        String url = String.format(WxApiTpye.DOMESTIC_REFUND_QUERY.getType(), refundNo);
        url = wxPayConfig.getDomain().concat(url);

        // 创建get请求
        HttpGet httpGet = new HttpGet(url);
        httpGet.setHeader("Accept", "application/json");

        // 完成签名并执行请求
        CloseableHttpClient httpClient = wxPayConfig.getHttpClient();
        //完成签名并执行请求
        CloseableHttpResponse response = httpClient.execute(httpGet);
        try {
            String bodyAsString = EntityUtils.toString(response.getEntity());
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) {
                log.info("查询退款单接口调用,返回结果 = " + bodyAsString);
            } else if (statusCode == 204) {
                log.info("查询退款单接口调用...没返回");
            } else {
                log.info("查询退款单接口调用,响应码 = " + statusCode + ", 返回结果 = " + bodyAsString);
                throw new IOException("request failed");
            }
            return bodyAsString;
        } finally {
            response.close();
            httpClient.close();
        }
    }

    //开启事务,回滚控制: 表示如果在该方法执行过程中抛出了任意类型的异常（Exception 及其子类），那么 Spring 将自动回滚在此方法中进行的所有数据库操作
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void processRefund(Map<String, Object> bodyMap) throws Exception {
        log.info("退款通知");

        // 解密报文
        String plainText = decryptFromResource(bodyMap);
        // 将明文转map
        // 使用Fastjson 将 字符串转换为Map
        Map<String, Object> plainTextMap = JSON.parseObject(plainText, HashMap.class);

        String orderNo = (String) plainTextMap.get("out_trade_no");//商户系统内部订单号

        /*在对业务数据进行状态检查和处理之前，要采用数据锁进行并发控制，以避免函数重入造成的数据混乱*/
        // 尝试获取锁: 成功获取则立即返回true，获取失败则立即返回false, 不必一直等待锁的释放
        if (lock.tryLock()) {// 处理通知并发问题

            try {
                // 处理重复的通知
                // 接口调用的幂等性: 无论接口被调用多少次，产生的结果是一致的.
                String orderStatus = orderInfoService.getOrderStatus(orderNo); // 获取订单状态
                // 判断订单状态
                if (!OrderStatus.REFUND_PROCESSING.getType().equals(orderStatus)) {
                    return;// 不是正在退款中的订单 ,直接返回,不处理
                }

                // 更新订单状态
                orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.REFUND_SUCCESS);

                // 更新退款单
                refundInfoService.updateRefund(plainText);
            } finally {
                // 主动释放锁
                lock.unlock();
            }
        }


    }

    @Override
    public String queryBill(String billDate, String type) throws Exception {
        log.warn("申请账单接口调用 {}", billDate);

        String url = "";
        if ("tradebill".equals(type)){
            url = WxApiTpye.TRADE_BILLS.getType();
        }else if ("fundflowbill".equals(type)){
            url = WxApiTpye.FUND_FLOW_BILL.getType();
        }else {
            throw new RuntimeException("不支持的账单类型");
        }

        url = wxPayConfig.getDomain().concat(url).concat("?bill_date=").concat(billDate);
        // 创建get请求
        HttpGet httpGet = new HttpGet(url);
        httpGet.setHeader("Accept", "application/json");

        // 完成签名并执行请求
        CloseableHttpClient httpClient = wxPayConfig.getHttpClient();
        //完成签名并执行请求
        CloseableHttpResponse response = httpClient.execute(httpGet);
        try {
            String bodyAsString = EntityUtils.toString(response.getEntity());
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) {
                log.info("成功200,申请账单接口调用,返回结果 = " + bodyAsString);
            } else if (statusCode == 204) {
                log.info("成功204,申请账单接口调用...没返回");
            } else {
                log.info("失败, 申请账单接口调用,响应码 = " + statusCode + ", 返回结果 = " + bodyAsString);
                throw new IOException("申请账单异常,响应码 = " + statusCode + ", 返回结果 = " + bodyAsString);
            }

            //获取账单下载地址
            // 字符串转map
            Map<String,String> resultMap = JSON.parseObject(bodyAsString, HashMap.class);

            return resultMap.get("download_url");
        } finally {
            response.close();
           // httpClient.close();
        }

    }

    // 下载账单
    @Override
    public String downloadBill(String billDate, String type) throws Exception {
        log.warn("下载账单接口调用 {}, {}", billDate,type);
        // 获取账单url地址
        String downloadUrl = this.queryBill(billDate, type);

        // 创建get请求
        HttpGet httpGet = new HttpGet(downloadUrl);
        httpGet.setHeader("Accept", "application/json");

        // 完成签名并执行请求
        CloseableHttpClient httpClient = wxPayConfig.getHttpClient();
        //完成签名并执行请求
        CloseableHttpResponse response = httpClient.execute(httpGet);
        try {
            String bodyAsString = EntityUtils.toString(response.getEntity());
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) {
                log.info("成功200,下载账单接口调用,返回结果 = " + bodyAsString);
            } else if (statusCode == 204) {
                log.info("成功204,下载账单接口调用...没返回");
            } else {
                log.info("失败, 下载账单接口调用,响应码 = " + statusCode + ", 返回结果 = " + bodyAsString);
                throw new IOException("下载账单接口调用,响应码 = " + statusCode + ", 返回结果 = " + bodyAsString);
            }

            return bodyAsString;
        } finally {
            response.close();
            // httpClient.close();
        }
    }

    /**
     * 微信关单接口的调用
     * 文档 https://pay.weixin.qq.com/docs/merchant/apis/native-payment/close-order.html
     *
     * @param orderNo
     */
    private void closeOrder(String orderNo) throws Exception {
        log.info("关单接口的调用,订单号==> {}", orderNo);
        // 替换占位符  ,构建关单地址
        String url = String.format(WxApiTpye.CLOSE_ORDER_BY_NO.getType(), orderNo);
        url = wxPayConfig.getDomain().concat(url); // 拼接
        // 创建远程请求对象
        HttpPost httpPost = new HttpPost(url);
        // 构建请求参数
        // 组装Json请求体
        Map<String, String> paramsMap = new HashMap<>();
        paramsMap.put("mchid", wxPayConfig.getMchId()); // 直连商户号
        // 使用Fastjson将 Map转换为JSON 字符串
        String jsonParams = JSON.toJSONString(paramsMap);
        log.info("请求体参数==> {}", jsonParams);

        //将请求参数设置到请求对象中
        StringEntity entity = new StringEntity(jsonParams, "utf-8");
        entity.setContentType("application/json");
        // 设置请求头 HTTP头参数
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");

        // 完成签名并执行请求
        CloseableHttpClient httpClient = wxPayConfig.getHttpClient();
        //完成签名并执行请求
        CloseableHttpResponse response = httpClient.execute(httpPost);
        try {

            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) {
                log.info("微信关单接口调用成功200");
            } else if (statusCode == 204) {
                log.info("微信关单接口调用成功204...没返回");
            } else {
                log.info("微信关单接口调用失败,响应码 = " + statusCode);
                throw new IOException("request failed");
            }

        } finally {
            response.close();
            httpClient.close();
        }

    }

    /**
     * 对称解密
     * https://pay.weixin.qq.com/docs/merchant/apis/native-payment/payment-notice.html
     *
     * @param bodyMap
     * @return
     */
    private String decryptFromResource(Map<String, Object> bodyMap) throws GeneralSecurityException {
        log.info("密文解密");
        // 通知资源数据
        Map<String, String> resourceMap = (Map<String, String>) bodyMap.get("resource");
        // 数据密文
        String ciphertext = resourceMap.get("ciphertext");
        // 加密使用的随机串
        String nonce = resourceMap.get("nonce");
        // 附加数据。
        String associatedData = resourceMap.get("associated_data");

        log.info("密文==> {}", ciphertext);
        // 用 APIv3密钥 来解密
        AesUtil aesUtil = new AesUtil(wxPayConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8));
        // 明文
        String plainText = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),
                nonce.getBytes(StandardCharsets.UTF_8),
                ciphertext);
        log.info("明文==> {}", plainText);

        return plainText;
    }

    /**
     * 调用Native支付接口 配置参数
     *
     * @param orderInfo
     * @return
     * @throws Exception
     */
    public Map<String, Object> requestNativePay(OrderInfo orderInfo) throws Exception {
        log.info("调用微信Native支付... ");
        //请求URL
        HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(WxApiTpye.NATIVE_PAY.getType()));

        // 参数官方文档 网址: https://pay.weixin.qq.com/docs/merchant/apis/native-payment/direct-jsons/native-prepay.html
        // API文档 https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_1.shtml
        // 请求body参数
        Map<String, Object> paramsMap = new HashMap<>();
        paramsMap.put("appid", wxPayConfig.getAppid());// 公众号的APPID
        paramsMap.put("mchid", wxPayConfig.getMchId());// 直连商户的商户号
        paramsMap.put("description", orderInfo.getTitle());// 商品描述
        paramsMap.put("out_trade_no", orderInfo.getOrderNo());// 商户订单号
        // 商户提供 微信给商户通知的地址
        paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxNotifyType.NATIVE_NOTIFY.getType()));// 通知地址

        Map<String, Object> amountMap = new HashMap<>();
        amountMap.put("total", 1);// 总金额 单位:分
        amountMap.put("currency", "CNY");// 货币类型  CNY：人民币

        paramsMap.put("amount", amountMap);// 订单金额信息 单位:分
        // 请求body参数
        // 使用Fastjson将Map转换为JSON字符串
        String reqdata = JSON.toJSONString(paramsMap);

        System.out.println("请求body支付参数: " + reqdata);

        StringEntity entity = new StringEntity(reqdata, "utf-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");

        CloseableHttpClient httpClient = wxPayConfig.getHttpClient();
        //完成签名并执行请求
        CloseableHttpResponse response = httpClient.execute(httpPost);
        try {
            String bodyAsString = EntityUtils.toString(response.getEntity());
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) {
                log.info("Native下单成功,返回结果 = " + bodyAsString);
            } else if (statusCode == 204) {
                log.info("Native下单成功...没返回");
            } else {
                log.info("Native下单失败,响应码 = " + statusCode + ", 返回结果 = " + bodyAsString);
                throw new IOException("request failed");
            }
            // 响应结果
            // 使用Fastjson 将 字符串转换为Map
            Map<String, String> resultMap = JSON.parseObject(bodyAsString, HashMap.class);
            // 取出二维码地址
            String codeUrl = resultMap.get("code_url");

            // 保存二维码地址
            orderInfoService.saveCodeUrl(orderInfo.getOrderNo(), codeUrl);

            // 返回二维码地址给前端
            Map<String, Object> map = new HashMap<>();
            map.put("codeUrl", codeUrl);
            map.put("orderOn", orderInfo.getOrderNo());

            return map;
        } finally {
            response.close();
            httpClient.close();
        }
    }

    /**
     * 测试 调起支付 获取二维码
     * <p>
     * Native 支付下单为例
     */
    public void getCodeTest() {

        // 自动更新的 微信支付平台证书
        Config config = wxPayConfig.getConfig();
        // 构建service
        NativePayService service = new NativePayService.Builder().config(config).build();

        // 参数文档地址 https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_1.shtml
        // request.setXxx(val)设置所需参数，具体参数可见Request定义
        PrepayRequest request = new PrepayRequest();
        // 商品金额 必填
        Amount amount = new Amount();
        amount.setTotal(1);

        request.setAmount(amount); //商品金额 单位: 分
        request.setAppid("wxa9d9651ae******"); // 公众号的APPID
        request.setMchid("190000****");// 直连商户的商户号
        request.setDescription("测试商品标题");// 商品标题名称
        request.setNotifyUrl("https://notify_url"); // 通知地址 商户提供
        request.setOutTradeNo("out_trade_no_001"); // 商户订单号
        // 调用下单方法，得到应答
        PrepayResponse response = service.prepay(request);
        // 使用微信扫描 code_url 对应的二维码，即可体验Native支付
        System.out.println(response.getCodeUrl());
    }


}
